﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SWF;

namespace SWF.Runtime
{

    //3d Master Matrix for the clip

    public class Clip
    {
        //----------------------------------------------------------------------
        private bool _Repeat;
        private bool _Playing;

        private double _FrameDuration;

        private Frame[] _TimeLine;
        private double _CurrentTime;
        private double _DeltaTime;
        public uint _CurrentFrame;

        //Actionscript context
        private Stack<object> _ActionStack;
        private Dictionary<string, object> _Variables;
        private List<string> _ConstantPool;
        private Dictionary<string, List<IAction>> _FunctionMap;

        private Color _BackgroundColor;
        
        public int InstructionPointer;

        public bool _UseNetwork = false;

        private Dictionary<uint, KeyValuePair<IContent, PlaceOperation>> _ActiveContentList;

        //----------------------------------------------------------------------
        public Clip()
        {
            _ActiveContentList = new Dictionary<uint, KeyValuePair<IContent, PlaceOperation>>();
            _ActionStack = new Stack<object>();
            _Variables = new Dictionary<string, object> { };
            _FunctionMap = new Dictionary<string, List<IAction>>();
            _ConstantPool = new List<string>();
            _Repeat = false;
            _Playing = true;
            _CurrentFrame = 0;
            _DeltaTime = 0.0;
            _CurrentTime = 0.0;
        }
        //----------------------------------------------------------------------
        public bool Load(string filename)
        {
            FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
            BinaryReader binaryReader = new BinaryReader(fileStream);

            SwfReader reader = new SwfReader(binaryReader.ReadBytes((int)fileStream.Length));
            SwfCompilationUnit compilationUnit = new SwfCompilationUnit(reader);

            _TimeLine = new Frame[compilationUnit.Header.FrameCount];
            _FrameDuration = 1 / compilationUnit.Header.FrameRate;
            _CurrentFrame = 0;
            _TimeLine[_CurrentFrame] = Frame.CreateFrame();

            foreach (ISwfTag tag in compilationUnit.Tags)
            {
                _ParseTag(tag);
            }

            _CurrentFrame = 0;
            _UpdateFrame();
            return true;
        }
        //----------------------------------------------------------------------
        public void Update(GameTime gameTime)
        {
            if (!_Playing)
            {
                return;
            }

            _DeltaTime += gameTime.ElapsedGameTime.TotalSeconds;
            _CurrentTime += gameTime.ElapsedGameTime.TotalSeconds;
            
            if (_DeltaTime > _FrameDuration)
            {
                _DeltaTime -= _FrameDuration;
                if (_CurrentFrame + 2 > _TimeLine.Length)
                {
                    if (_Repeat)
                    {
                        _CurrentFrame = 0;
                    }
                    else
                    {
                        _Playing = false;
                        return;
                    }
                }
                else
                { 
                    ++_CurrentFrame;
                }

                _UpdateFrame();

                foreach (KeyValuePair<IContent, PlaceOperation> eContent in _ActiveContentList.Values.Where(element => element.Key is IFrameContainer))
                {
                    ((IFrameContainer)eContent.Key).NextFrame();
                }
            }
        }
        //----------------------------------------------------------------------
        public void Render()
        {
            foreach (KeyValuePair<IContent, PlaceOperation> eContent in _ActiveContentList.Values.OrderByDescending(element => -element.Value.Depth))
            {
                IRenderable ir = eContent.Key as IRenderable;
                if (ir != null)
                {
                    ir.Render(eContent.Value.PlaceMatrix);
                }
            }
        }

        //----------------------------------------------------------------------
        public void Stop()
        {
            _Playing = false;
        }
        //----------------------------------------------------------------------
        public void Play()
        {
            _Playing = true;
        }

        //----------------------------------------------------------------------
        private void _UpdateFrame()
        {
            if (_CurrentFrame == 0) _ActiveContentList.Clear();

            foreach (PlaceOperation placeOp in _TimeLine[_CurrentFrame].PlaceObjectList)
            {
                if (SWFMain.ContentDictionary.ContainsKey(placeOp.Character))
                {
                    if (placeOp.Character != 0
                        && !_ActiveContentList.ContainsKey(placeOp.Depth))
                    {
                        _ActiveContentList.Add(placeOp.Depth, new KeyValuePair<IContent, PlaceOperation>(SWFMain.ContentDictionary[placeOp.Character], placeOp));
                    }
                    _ActiveContentList[placeOp.Depth] = new KeyValuePair<IContent, PlaceOperation>(_ActiveContentList[placeOp.Depth].Key, placeOp);
                }
            }
            foreach (RemoveOperation removeOp in _TimeLine[_CurrentFrame].RemoveObjectList)
            {
                if (removeOp.Character != 0) _ActiveContentList.Remove(removeOp.Depth);
                else _ActiveContentList.Remove(removeOp.Depth);
            }

            foreach (List<IAction> actionOpList in _TimeLine[_CurrentFrame].ActionList)
            {
                for (InstructionPointer = 0; InstructionPointer < actionOpList.Count; ++InstructionPointer)
                {
                    actionOpList[InstructionPointer].Execute(this, _ActionStack, _Variables, _ConstantPool, _FunctionMap);
                }
            }
            if (_Variables.ContainsKey("onEnterFrame"))
            {
                List<IAction> lActionList = _Variables["onEnterFrame"] as List<IAction>;
                foreach (IAction lAction in lActionList)
                {
                    lAction.Execute(this, _ActionStack, _Variables, _ConstantPool, _FunctionMap);
                }
            }
        }
        //----------------------------------------------------------------------
        private void _ParseTag(ISwfTag tag)
        {
            switch (tag.TagType)
            {
                case TagType.FileAttributes:
                {
                    FileAttributesTag fileAtrributes = (FileAttributesTag)tag;
                    _UseNetwork = fileAtrributes.UseNetwork;
                    break;
                }
                case TagType.BackgroundColor:
                {
                    BackgroundColorTag backgroundColor = (BackgroundColorTag)tag;
                    _BackgroundColor = new Color(
                        (float)backgroundColor.Color.R / 255f,
                        (float)backgroundColor.Color.G / 255f,
                        (float)backgroundColor.Color.B / 255f,
                        (float)backgroundColor.Color.A / 255f);
                    break;
                }
                case TagType.End:
                {
                    break;
                }
                case TagType.DefineSprite:
                {
                    Sprite sprite = new Sprite((DefineSpriteTag)tag);
                    SWFMain.ContentDictionary.Add(sprite.Id, sprite);
                    break;
                }
                case TagType.PlaceObject:
                case TagType.PlaceObject2:
                case TagType.PlaceObject3:
                {
                    _TimeLine[_CurrentFrame].PlaceObjectList.Add(PlaceOperation.CreateFromTag(tag));
                    break;
                }
                case TagType.RemoveObject:
                case TagType.RemoveObject2:
                {
                    _TimeLine[_CurrentFrame].RemoveObjectList.Add(RemoveOperation.CreateFromTag(tag));
                    break;
                }
                case TagType.ShowFrame:
                {
                    if (_CurrentFrame + 2 > _TimeLine.Length)
                    {
                        _CurrentFrame = 0;
                    }
                    else
                    {
                        _TimeLine[++_CurrentFrame] = Frame.CreateFrame();
                    }
                    break;
                }
                case TagType.FrameLabel:
                {
                    FrameLabelTag frameLabel = (FrameLabelTag)tag;
                    _TimeLine[_CurrentFrame].Name = frameLabel.TargetName;
                    break;
                }
                case TagType.DefineShape:
                case TagType.DefineShape2:
                case TagType.DefineShape3:
                case TagType.DefineShape4:
                {
                    Shape shape = new Shape(tag);
                    if (shape.Generated)
                    {
                        SWFMain.ContentDictionary.Add(shape.Id, shape);
                    }
                    break;
                }
                case TagType.DoAction:
                {
                    List<IAction> actions = ((DoActionTag)tag).ActionRecords.Statements;
                    _TimeLine[_CurrentFrame].ActionList.Add(actions);
                    break;
                }
                case TagType.DoInitAction:
                {
                    break;
                }
                case TagType.DefineButton:
                case TagType.DefineButton2:
                {
                    break;
                }
                case TagType.DefineButtonCxform:
                {
                    break;
                }
                case TagType.JPEGTables:
                {
                    break;
                }
                case TagType.DefineBits:
                {
                    break;
                }
                case TagType.DefineBitsJPEG2:
                {
                    DefineBitsTag bitsTag = (DefineBitsTag)tag;

                    break;
                }
                case TagType.DefineBitsJPEG3:
                {
                    DefineBitsTag bitsTag = (DefineBitsTag)tag;
                    TextureContent lTextureContent = new TextureContent();
                    lTextureContent.Texture = bitsTag.GenerateTexture2D();
                    SWFMain.ContentDictionary.Add(bitsTag.CharacterId, lTextureContent);
                    break;
                }
                case TagType.DefineBitsLossless:
                case TagType.DefineBitsLossless2:
                {
                    break;
                }
                case TagType.DefineSound:
                case TagType.StartSound:
                case TagType.SoundStreamHead:
                case TagType.SoundStreamBlock:
                {
                    //Audio Not Supported
                    break;
                }
                case TagType.DefineFontInfo:
                case TagType.DefineFontInfo2:
                {
                    break;
                }
                case TagType.DefineFont:
                case TagType.DefineFont2:
                case TagType.DefineFont3:
                {
                    break;
                }
                case TagType.DefineFontAlignZones:
                {
                    break;
                }
                case TagType.CSMTextSettings:
                {
                    break;
                }
                case TagType.DefineText:
                case TagType.DefineText2:
                {
                    break;
                }
                case TagType.DefineEditText:
                {
                    break;
                }
                case TagType.ExportAssets:
                {
                    break;
                }
                case TagType.UnsupportedDefinition:
                {
                    break;
                }
            }
        }
    }
}
